iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0

上一篇我們安裝了 Flask-SQLAlchemy,也設定好要連到哪個資料庫:

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
db = SQLAlchemy(app)

不過目前資料庫裡面還是空的,因為我們還沒有告訴它「作業這種資料長什麼樣子」。

今天就來建立第一個 Model,並且把資料表真的建出來。

Model 是什麼?

Day 19 提到 ORM 的對應關係:

資料表(Table) → 類別(class)
一筆資料(Row) → 物件
欄位(Column) → 屬性

其中那個「類別」,在 Flask-SQLAlchemy 裡就叫做 Model。

Model 可以理解成:

用 Python 的類別來描述一張資料表的結構。

這裡要注意一下名字:

Day 13 用過 api.model(Flask-RESTX 的資料模型)
今天的是 SQLAlchemy 的 Model

兩個都叫 model,但做的事情不一樣:

api.model → 描述 API 的輸入輸出格式,給 Swagger 和 marshal 用
SQLAlchemy Model → 描述資料庫的資料表結構

這是初學很容易搞混的地方,先分清楚會比較好。

建立 Task Model

在 app.py 裡,db 建立之後加上:

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    completed = db.Column(db.Boolean, nullable=False, default=False)

跟 Day 18 自己寫的 SQL 對照一下:

CREATE TABLE tasks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    completed INTEGER NOT NULL DEFAULT 0
);

會發現其實是同一件事,只是換成用 Python 寫。

一行一行看:

class Task(db.Model)

繼承 db.Model,表示這是一個資料表的 Model。

db.Column(...)

表示這是資料表的一個欄位。

db.Integer / db.String(100) / db.Boolean

欄位的型別:整數 / 最長 100 字的字串 / 布林值。

primary_key=True
這個欄位是主鍵,也就是 Day 18 說的「不重複的編號」。
設定成整數主鍵之後會自動編號,不用像 Day 14 那樣自己維護 next_id。

nullable=False
不可以是空的,對應 SQL 的 NOT NULL。

default=False
沒有給值時預設是 False。

這裡也可以順便注意一下:

SQLite 其實沒有布林型別(Day 18 提過,是用 0 和 1 存)。
但我們在 Model 寫 db.Boolean 之後,SQLAlchemy 會自動幫我們轉換,程式裡拿到的就是 Python 的 True / False。

這就是 ORM 幫我們省下的其中一件事。

資料表的名字會是什麼?

我們只寫了 class Task,沒有說資料表要叫什麼。

Flask-SQLAlchemy 會自動把類別名稱轉成小寫,所以這張表的名字會是:

task

如果想自己指定,可以在 class 裡面加上:

class Task(db.Model):
    __tablename__ = 'tasks'

這次就先用預設的 task。

把資料表真的建出來

寫好 Model 之後,資料庫裡還是不會自動出現這張表,要執行:

db.create_all()

不過不能直接寫在最外層,會出現錯誤,因為它需要知道是哪一個 Flask app 的設定。

正確的寫法是:

with app.app_context():
    db.create_all()

app_context 可以先簡單理解成「現在是在這個 Flask app 的環境裡面做事」。

Flask 有很多功能(包含資料庫連線設定)都需要知道目前是哪個 app。
在處理 Request 的時候 Flask 會自己準備好,但像現在這樣在請求以外的地方操作資料庫,就要自己包一層 app_context。

這是初學很常遇到的錯誤訊息之一:

RuntimeError: Working outside of application context.

看到它的時候,通常就是少了 with app.app_context()。

目前的完整程式

app.py

from flask import Flask
from flask_restx import Api, Resource, fields
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'

db = SQLAlchemy(app)
api = Api(app, title='Task API', version='1.0', description='30 天鐵人賽練習用的作業管理 API')

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    completed = db.Column(db.Boolean, nullable=False, default=False)

task_input = api.model('TaskInput', {
    'name': fields.String(required=True, description='作業名稱', example='洗碗'),
    'completed': fields.Boolean(required=True, description='是否完成', example=False),
})

task_response = api.model('TaskResponse', {
    'id': fields.Integer(readonly=True, description='作業編號'),
    'name': fields.String(description='作業名稱'),
    'completed': fields.Boolean(description='是否完成'),
})

@api.route('/tasks')
class TaskList(Resource):
    @api.marshal_list_with(task_response)
    def get(self):
        """取得所有作業"""
        return []

with app.app_context():
    db.create_all()

if __name__ == '__main__':
    app.run(debug=True)

今天先把 API 的部分留一個空的 GET,Day 21 再讓它真的去查資料庫。

執行看看

python app.py

畫面上看起來沒什麼特別的,但是打開專案資料夾會發現多了:

instance/tasks.db
這就是 Day 19 說的:SQLite 檔案會建在 instance 資料夾裡面
https://ithelp.ithome.com.tw/upload/images/20261004/201838655oXjYbxny9.png

確認資料表真的建好了

資料庫檔案出現了,但裡面真的有 task 這張表嗎?

可以用 Day 18 學過的 sqlite3 來確認。

建立一個檔案:

check_db.py

import sqlite3

conn = sqlite3.connect('instance/tasks.db')
print("=== 資料庫裡有哪些表 ===")
tables = conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
print(tables)
print("=== task 這張表有哪些欄位 ===")
columns = conn.execute("PRAGMA table_info(task)").fetchall()

for column in columns:
    print(column)

conn.close()

執行:

python check_db.py

會看到類似這樣:

=== 資料庫裡有哪些表 ===
[('task',)]
=== task 這張表有哪些欄位 ===
(0, 'id', 'INTEGER', 1, None, 1)
(1, 'name', 'VARCHAR(100)', 1, None, 0)
(2, 'completed', 'BOOLEAN', 1, None, 0)

https://ithelp.ithome.com.tw/upload/images/20261004/201838650v7HGJGZgA.png
我們只寫了一個 Python 類別,Flask-SQLAlchemy 就幫我們把資料表建出來了。

一個要先知道的限制

db.create_all() 只會建立「還不存在」的資料表。

如果表已經存在,它不會去修改。

也就是說,之後如果我們在 Model 裡多加一個欄位,例如:

due_date = db.Column(db.String(20))

再執行一次 db.create_all(),資料庫不會多出這個欄位,程式反而會出現「找不到欄位」的錯誤。

這時候要嘛砍掉資料庫檔案重建(資料就沒了),要嘛使用 Migration。

Day 23 會介紹 Flask-Migrate 來處理這個問題,現在先知道有這個限制就好。

今天完成了:

認識 Model:用 Python 類別描述資料表
建立 Task Model,對照 Day 18 的 CREATE TABLE
認識 db.create_all() 和 app_context
確認 instance/tasks.db 裡真的多了一張 task 表

資料表有了,但是 API 現在還完全沒有跟它接起來,POST 進來的資料也還沒有真的存進去。

下一篇就來讓 POST /tasks 真的把資料寫進資料庫,並且驗證一件事:

重新啟動 Server 之後,資料還在不在。


上一篇
Day 19|Flask 怎麼連資料庫?
下一篇
Day 21|POST 真的存進資料庫
系列文
從零開始的後端開發:用 Flask 實作 REST API,搞懂 API 與資料庫之間如何協作 共 23 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言